using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace BloodBot
{
    public class Camera : Microsoft.Xna.Framework.GameComponent
    {
        public Matrix view { get; protected set; }
        public Matrix projection { get; protected set; }
        // Camera Vectors
        public Vector3 cameraPosition { get; protected set; }
        private Vector3 prevCameraPosition;
        public Vector3 cameraDirection;
            Vector3 cameraUp;
        float speed = 0.007f;
        Vector3 velocity = new Vector3(0,0,0);

        bool freeLook = false;

        public BoundingFrustum frustrum;
        public BoundingSphere boundingSphere;

        public Collisions collisions;
        public Ray collisionRay;

        Input input;

        // Max pitch variables
        float totalPitch = MathHelper.PiOver4 / 1.1f;
        float currentPitch = 0; 

        public Camera(Game game, Input input, Vector3 pos, Vector3 target, Vector3 up)
            : base(game)
        {
            //Build camera view matrix
            cameraPosition = pos;
            cameraDirection = target - pos;
            cameraDirection.Normalize();
            cameraUp = up;
            createLookAt();
            this.input = input;

            projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4,
                (float)Game.Window.ClientBounds.Width /
                (float)Game.Window.ClientBounds.Height,
                0.2f, 100);
            collisions = new Collisions(game);
            collisionRay = new Ray(pos, new Vector3(0, 0, 0));
        }

        public override void Initialize()
        {
            boundingSphere = new BoundingSphere(cameraPosition+=velocity, 1.0f);
            base.Initialize();
        }

        public void Update(GameTime gameTime, Collisions collisions, float groundSize, Map map)
        {
            float dt = gameTime.ElapsedGameTime.Milliseconds;
            velocity = Vector3.Zero;
            prevCameraPosition = cameraPosition;
            frustrum = new BoundingFrustum(view * projection);
            boundingSphere.Center = (cameraPosition += velocity);

            double lastUpdate = gameTime.ElapsedGameTime.TotalMilliseconds;
            float updateAmount = speed * (float)(lastUpdate / 1000f);

            float camDirX = cameraDirection.X;
            float camDirZ = cameraDirection.Z;

            if (input.RSwitch())
                reset(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up);

            if (freeLook)
            {
                if (input.Forward())
                    velocity += speed * cameraUp;
                if (input.Backward())
                    velocity -= speed * cameraUp;
                if (input.Left())
                    velocity += speed * (Vector3.Cross(cameraUp, cameraDirection));
                if (input.Right())
                    velocity -= speed * (Vector3.Cross(cameraUp, cameraDirection));

            }
            if (!freeLook)
            {
                //put camera back on the ground
                if (cameraPosition.Y > 1)
                    cameraPosition += new Vector3(0, -1, 0);
                //limit movement to only x and z axis

                Vector3 startingPosition = new Vector3();
                startingPosition.X = cameraPosition.X;
                startingPosition.Y = cameraPosition.Y;
                startingPosition.Z = cameraPosition.Z;

                if (input.Forward())
                {

                    velocity += speed * new Vector3(cameraDirection.X, 0, cameraDirection.Z);
                    boundingSphere.Center = (cameraPosition);
                }
                if (input.Backward())
                {
                    velocity += speed * -new Vector3(cameraDirection.X, 0, cameraDirection.Z);

                }
                if (input.Left())
                {
                    velocity += speed * (Vector3.Cross(cameraUp, new Vector3(cameraDirection.X, 0, cameraDirection.Z)));


                }
                if (input.Right())
                {
                    velocity += speed * -(Vector3.Cross(cameraUp, new Vector3(cameraDirection.X, 0, cameraDirection.Z)));
                }
            }

            if (Game.IsActive)
            {
                if (input.MouseMoved())
                {
                    //Yaw Rotations
                    cameraDirection = Vector3.Transform(cameraDirection, Matrix.CreateFromAxisAngle(cameraUp, (-MathHelper.PiOver4 / 150) *
                        (input.MouseXDifference())));

                    //Pitch Rotations
                    float pitchAngle = (MathHelper.PiOver4 / 150) * (input.MouseYDifference());
                    Vector3 axis = Vector3.Cross(cameraUp, cameraDirection);
                    axis.Normalize();

                    if (Math.Abs(currentPitch + pitchAngle) < totalPitch)
                    {
                        cameraDirection = Vector3.Transform(cameraDirection, Matrix.CreateFromAxisAngle(axis, pitchAngle));
                        currentPitch += pitchAngle;
                    }

                }

                createLookAt();
            }


            collisionRay.Position = cameraPosition;
            collisionRay.Direction = velocity;
            collisionRay.Direction.Normalize();
            checkWallCollisions(map.getWalls(), gameTime);

            if (Vector3.Distance((cameraPosition + velocity * dt), new Vector3(0, 1.0f, 0)) >=
                Vector3.Distance(new Vector3(groundSize, 1.0f, 0), new Vector3(0, 1.0f, 0))
                && !freeLook)
                velocity = Vector3.Zero;

            if (velocity != Vector3.Zero)
            {
                cameraPosition += velocity * dt;
            }
            base.Update(gameTime);
        }


        private Vector3 moveToWall(BoundingSphere bs, BoundingBox bb)
        {
            //this is the golden algorithm, to move the sphere to the closest position to the wall without colliding
            Vector3 centerPoint = Vector3.Zero;


            return centerPoint;
        }

        private void checkFutureCollision(Vector3 futurePos)
        {
        }

        private void createLookAt()
        {
            view = Matrix.CreateLookAt(cameraPosition, cameraPosition + cameraDirection, cameraUp);
        }

        public void checkWallCollisions(List<Cell> wallList, GameTime gameTime)
        {
            float dt = gameTime.ElapsedGameTime.Milliseconds;
            Vector3 wBoxCenter = new Vector3(0,0,0);
            float? distance = 0.0f;
            int wallsCount = 0;
            foreach (Cell w in wallList)
            {
                BoundingBox wBox = w.getBoundingBox();
                distance = collisionRay.Intersects(wBox);
                
                if (distance != null)
                {
                    
                    if (distance < 0.9f)
                    {
                        wallsCount++;
                        Vector3 PointOfIntersection = cameraPosition + (collisionRay.Direction * (float)distance);

                        //compare the PointOfIntersection to the bounding Box to find collision wall of the bounding box.
                        //if to the left, move the camera left. if to the right, move the camera right, and so on.
                        // or just negate the direction of movement because you hit something
                        if (PointOfIntersection.X == wBox.Max.X)
                        {   //approaching from +x;
                            //Halt velocity along X;
                            velocity = new Vector3(0, 0, velocity.Z);
                        }
                        if (PointOfIntersection.X == wBox.Min.X)
                        {   //approaching from -x
                            velocity = new Vector3(0, 0, velocity.Z);
                        }
                        if (PointOfIntersection.Z == wBox.Max.Z)
                        {   //approaching from +z
                            velocity = new Vector3(velocity.X, 0, 0);
                        }
                        if (PointOfIntersection.Z == wBox.Min.Z)
                        {   //approaching from -z
                            velocity = new Vector3(velocity.X, 0, 0);
                        }
                    }
                }
                
            }
        }

        public void reset(Vector3 pos, Vector3 target, Vector3 up)
        {
            //Build camera view matrix
            cameraPosition = pos;
            cameraDirection = target - pos;
            cameraDirection.Normalize();
            cameraUp = up;
            createLookAt();
        }

        public void toggleFreeLook()
        {
            if (freeLook == true)
            { freeLook = false; return; }

            if (freeLook == false)
            { freeLook = true; return; }
        }
        public float getSpeed()
        {
            return speed;
        }
    }
}
